﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;
using System.Data.SqlClient;
using DefaulArchitecture.Domain;
using DefaulArchitecture.Exception;

namespace DefaulArchitecture.DAL
{
    public sealed class DALTransaction : IDisposable
    {
        #region Attributes

        private delegate void EndTransactionDelegate();
        private string connStr = ConfigurationManager.ConnectionStrings["ConnStr"].ConnectionString;
        private EndTransactionDelegate _EndTransaction;
        private SqlConnection _connection;
        private SqlTransaction _transaction;
        private bool isFinished;

        #endregion

        #region Constructors

        /// <summary>
        /// Will not be used
        /// </summary>
        private DALTransaction() { }

        /// <summary>
        /// Starts the SqlConnection and SqlTransaction, setting the SqlTransaction value
        /// for all ITransaction instances.
        /// Also assigns the values of EndTransaction methods in the endTransaction delegate object.
        /// </summary>
        /// <param name="values">ITransaction objects that will be part of the transaction</param>
        private DALTransaction(params IDALTransaction[] values)
        {
            this.connStr = values[0].ConnectionString;
            this.isFinished = false;
            this._connection = new SqlConnection(this.connStr);
            this.OpenConnection();
            this._transaction = this._connection.BeginTransaction();

            foreach (IDALTransaction i in values)
            {
                i.BeginTransaction(this._transaction);
                _EndTransaction += new EndTransactionDelegate(i.EndTransaction);
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Starts and returns a new Transaction instance.
        /// </summary>
        /// <param name="values">ITransaction objects that will be part of the transaction</param>
        /// <returns>A new Transaction instance</returns>
        public static DALTransaction BeginTransaction(params IDALTransaction[] values)
        {
            if (values != null && !values.Contains(null))
                return new DALTransaction(values);
            else
                throw new ArgumentNullException("values", "The argument must receive a value. It can not contain null values!");
        }

        /// <summary>
        /// Commits and finish the transaction.
        /// </summary>
        public void CommitTransaction()
        {
            if (!this.isFinished)
            {
                this._transaction.Commit();
                this.CloseConnection();
            }
            else
            {
                throw new DALException("The transaction was finished!", "DALTransaction", "CommitTransaction");
            }
        }

        /// <summary>
        /// Rollbacks and cancels the transaction
        /// </summary>
        public void RollbackTransaction()
        {
            if (!this.isFinished)
            {
                this._transaction.Rollback();
                this.CloseConnection();
            }
            else
            {
                throw new DALException("The transaction was finished!", "DALTransaction", "RollbackTransaction");
            }
        }

        /// <summary>
        /// If the transaction is not finished, it will be canceled and finished.
        /// </summary>
        public void Dispose()
        {
            if (!this.isFinished)
                this.RollbackTransaction();
        }

        /// <summary>
        /// Opens the connection.
        /// </summary>
        private void OpenConnection()
        {
            if (_connection.State != System.Data.ConnectionState.Open)
                _connection.Open();
        }

        /// <summary>
        /// Closes and disposes the connection.
        /// Also disposes the transaction and call the EndTransaction delegate.
        /// </summary>
        private void CloseConnection()
        {
            this.isFinished = true;
            this._EndTransaction();
            this._transaction.Dispose();

            if (_connection.State != System.Data.ConnectionState.Closed)
                _connection.Close();

            this._connection.Dispose();
        }

        #endregion
    }
}
